package com.ruoyi.activity.service.impl;

import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.activity.mapper.CommentMapper;
import com.ruoyi.activity.mapper.ContestMapper;
import com.ruoyi.activity.mapper.TemplateMapper;
import com.ruoyi.activity.service.IContestService;
import com.ruoyi.category.mapper.CategoryMapper;
import com.ruoyi.common.constant.ActivityTypeConstants;
import com.ruoyi.common.constant.ActivitysConstant;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.utils.BeanCopyUtils;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.model.auth.AuthContextHolder;
import com.ruoyi.model.category.Category;
import com.ruoyi.model.comment.Comment;
import com.ruoyi.model.contest.Contest;
import com.ruoyi.model.contest.dto.ContestDto;
import com.ruoyi.model.contest.vo.ContestAdminVo;
import com.ruoyi.model.contest.vo.ContestListVo;
import com.ruoyi.model.template.TemplateValue;
import com.ruoyi.model.contest.vo.ContestAndCommentVo;
import com.ruoyi.model.template.Template;
import com.ruoyi.model.user.UserInfo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;


/**
 * @author : 张怀秋
 **/
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
@Slf4j
public class ContestServiceImpl extends ServiceImpl<ContestMapper, Contest> implements IContestService {


    @Resource
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    /**
     * 分类服务
     */
    private final CategoryMapper categoryMapper;

    /**
     * 模板服务
     */
    private final TemplateMapper templateMapper;

    /**
     * 评论服务
     */
    private final CommentMapper commentMapper;

    /**
     * redis服务
     */
    @Resource
    private RedisCache redisCache;
    @Autowired
    private ContestMapper contestMapper;


    /**
     * 发布竞赛
     *
     * @param contestDto 竞赛Dto
     * @return 发布竞赛
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean pushContest(ContestDto contestDto) {
        contestDto.setId(null );
        //0.为了防止恶意用户Xss攻击 所以这里需要合法用户再次输入自身密码账户密码进行验证, 如果是管理员的话可以暂时的绕过这个验证
        String todayUUID = redisCache.getCacheObject("admin__UUID");
        // 管理员得到今天的uuid, 方面绕过密码验证
        // || 后面是方便测试使用
//        boolean isAdmin = StringUtils.equals(todayUUID, contestDto.getPassword());
        boolean isAdmin = StringUtils.equals("123456", contestDto.getPassword());
        if (!isAdmin) {
            if (!bCryptPasswordEncoder.matches(contestDto.getPassword(), AuthContextHolder.getUserInfo().getPassword())) {
                //不通过 直接抛出异常
                throw new RuntimeException("您的密码错误!");
            }

            //1. 获取到当前登录用户的信息 验证当前账号是否为组织账号, 或者是管理员账户
            UserInfo userInfo = AuthContextHolder.getUserInfo();
            String isOrganization = userInfo.getIsOrganization();

            if ("1".equals(isOrganization)) {
                //说明该账号不是组织者账号 不能发布竞赛 后端抛出异常 前端跳转页面提示用户
                throw new RuntimeException("您的账号并非组织账号 目前没有发布竞赛的权力 请申请为组织者账号!");
            }
        }

        //2. 在service校验参数 方便抛出异常
        checkValue(contestDto);

        //3. 对图片的路径和名称进行处理
//        contestDto.getImgNameList().forEach(name -> {
//            //TODO 后续将这些图片遍历存入图片表中
//        });
//        contestDto.getImgUrlList().forEach(url -> {
//            //TODO 后续将这些图片遍历存入图片表中
//        });

        //4.对象拷贝 将竞赛信息DTO拷贝进入实体类
        Contest contest = BeanCopyUtils.copyBean(contestDto, Contest.class);
        contest.setPushId(AuthContextHolder.getUserId()); //将当前用户id赋值给发布者id
        //如果不置顶是否为团队参赛直接默认为不是团队赛
        if (StringUtils.isEmpty(contest.getIsTeam())) {
            contest.setIsTeam("1"); //默认为不是团队参赛
        } else {
            contest.setTeamNum(contest.getTeamNum()); //设置参赛人数
        }
        contest.setParticipationNum(0L);
        contest.setViewNum(0);
        contest.setStatus("1"); //默认待审核状态
        contest.setIsTop("1");
        contest.setSort(-1); //代表没有设置
        contest.setPushTime(new Date()); //当前时间就是发布时间
        contest.setToll(contestDto.getToll() == null ? new BigDecimal("0") : contestDto.getToll());
        contest.setTeamNum(contestDto.getTeamNum() == null ? 0 : contestDto.getTeamNum());

        //顺便将发布者的姓名也存入数据库当中 这样可以防止很多麻烦的联表
        contest.setPushName(isAdmin ? "admin" : AuthContextHolder.getUserInfo().getName());

        //5. 保存数据库 交给后台进行审核
        boolean result = this.save(contest);

        //6. 如果发布者使用系统的报名模板那么需要把模板存入模板
        List<TemplateValue> teValue = contestDto.getTemplateValueList();
        String templateJson = JSONObject.toJSONString(teValue);

        //7.存入模板表中
        Template template = new Template();
        template.setTemplate(templateJson);
        template.setActivityId(contest.getId());
        template.setType(ActivityTypeConstants.CONTEST_TYPE);

        int eff = this.templateMapper.insert(template);

        return result && eff != 0;
    }

    /**
     * 校验参数是否合法
     *
     * @param contestDto 用户发布的竞赛表单
     */
    public static void checkValue(ContestDto contestDto) {

        log.warn("用户发布的竞赛表单   {}", contestDto);

        if (StringUtils.isEmpty(contestDto.getContent())) {
            throw new RuntimeException("竞赛详情不能为空!");
        }

        if (StringUtils.isEmpty(contestDto.getTitle())) {
            throw new RuntimeException("竞赛标题不能为空!");
        }

        if (StringUtils.isEmpty(contestDto.getOrganizer())) {
            throw new RuntimeException("主办方信息不能为空!");
        }

        if (StringUtils.isEmpty(contestDto.getCategoryId())) {
            throw new RuntimeException("竞赛分类不能为空!");
        }

        if (StringUtils.isEmpty(contestDto.getLocal())) {
            throw new RuntimeException("竞赛地址不能为空!");
        }

//        Assert.notNull(contestDto.getLocal(), "竞赛地址不能为空");

//        if (CollectionUtils.isEmpty(contestDto.getImgNameList())) {
//            throw new RuntimeException("图片名称不能为空!");
//        }
//
//        if (CollectionUtils.isEmpty(contestDto.getImgUrlList())) {
//            throw new RuntimeException("图片地址不能为空!");
//        }

        if (StringUtils.isEmpty(contestDto.getIsEmail())) {
            throw new RuntimeException("必须说明是否可以提交电子档案!");
        }

        if (StringUtils.isEmpty(contestDto.getIsToll())) {
            throw new RuntimeException("是否需要收费不能为空!");
        }

        if (contestDto.getStartDate() == null || contestDto.getEndDate() == null) {
            throw new RuntimeException("竞赛开始时间和结束时间不明确!");
        }

        if (contestDto.getEnrollStartDate() == null || contestDto.getEnrollEndDate() == null) {
            throw new RuntimeException("竞赛报名开始时间和结束时间不明确!");
        }

        if (StringUtils.isEmpty(contestDto.getIsComment())) {
            throw new RuntimeException("必须说明是否可以对竞赛进行评论!");
        }

        //校验收费是否为空
        if (contestDto.getIsToll().equals("0")) { //收费
            BigDecimal toll = contestDto.getToll();
            if (ObjectUtils.isEmpty(toll)) {
                throw new RuntimeException("需要收费的竞赛的费用不能为空!");
            }
        }

    }

    /**
     * 查询竞赛 -Dto版
     * */
    @Override
    public ContestDto selectWebContestDtoById(String id) {
        Contest contest = this.baseMapper.selectOne(Wrappers.<Contest>lambdaQuery(Contest.class).eq(Contest::getId, id).eq(Contest::getIsDel, "0"));
        if (contest == null) {
            return new ContestDto();
        }
        return assembleContestDto(contest);
    }
    // 转换
    private ContestDto assembleContestDto(Contest contest) {
        ContestDto dto = new ContestDto();
        dto.setId(contest.getId());
        dto.setCategoryId(contest.getCategoryId());
        dto.setCover(contest.getCover());
        dto.setContent(contest.getContent());
        dto.setDescription(contest.getDescription());
        dto.setEndDate(contest.getEndDate());
        dto.setIsTeam(contest.getIsTeam());
        dto.setPushId(contest.getPushId());
        dto.setEnrollEndDate(contest.getEnrollEndDate());
        dto.setTitle(contest.getTitle());
        dto.setLocal(contest.getLocal());
        dto.setStartDate(contest.getStartDate());
        dto.setEnrollStartDate(contest.getEnrollStartDate());
        dto.setIsComment(contest.getIsComment());
        dto.setIsEmail(contest.getIsEmail());
        dto.setIsToll(contest.getIsToll());
        dto.setIsSystem(Integer.parseInt(contest.getIsSystem()));
        dto.setPersonNum(contest.getPersonNum());
        dto.setOrganizer(contest.getOrganizer());
        return dto;
    }

    /**
     * 查询竞赛
     *
     * @param id 竞赛主键
     * @return 竞赛
     */
    @Override
    public Contest selectWebContestById(String id) {
        return this.baseMapper.selectWebContestById(id);
    }

    /**
     * 查询竞赛列表
     *
     * @param webContest 竞赛
     * @return 竞赛
     */
    @Override
    public List<Contest> selectWebContestList(Contest webContest) {
        webContest.setIsDel("0");
        if (webContest.getPersonNum() != null && webContest.getPersonNum() == 0) {
            webContest.setPersonNum(null);
        }
//        this.baseMapper.selectList(Wrappers.<Contest>lambdaQuery().eq(Contest::getCategoryId, webContest.getCategoryId()));
        List<Contest> contests = this.baseMapper.selectWebContestList(webContest);
        return contests;
    }

    /**
     * 船新版本 查询竞赛列表
     * @param webContest 竞赛
     * @return 展示前台的Vo
     */
    @Override
    public List<ContestAdminVo> selectWebContestListV2(Contest webContest) {
        log.warn("查询条件    {}", webContest);
        return assembleContestAdminVoList(selectWebContestList(webContest));
    }

    /**
     * 整体转换集合
     * @param contests
     * @return
     */
    private List<ContestAdminVo> assembleContestAdminVoList(List<Contest> contests) {
        List<ContestAdminVo> adminVoList = new ArrayList<>();
        for (Contest contest : contests) {
        adminVoList.add(assembleContestAdminVo(contest));
        }
        return adminVoList;
    }

    private ContestAdminVo assembleContestAdminVo(Contest contest) {
        ContestAdminVo adminVo = new ContestAdminVo();
        log.warn("原始数据:   {}", contest);
        /*Category category = categoryMapper.selectOne(Wrappers.<Category>lambdaQuery().eq(Category::getId, contest.getCategoryId()));
        adminVo.setContestCategory(category == null ? "未知变量" : category.getCategoryName());*/
        adminVo.setContestCategory(contest.getCategoryId());
        adminVo.setContestInfo(contest.getDescription());
        adminVo.setId(contest.getId());
        adminVo.setLocal(contest.getLocal());
        adminVo.setTitle(contest.getTitle());
        adminVo.setEndDate(contest.getEndDate());
        adminVo.setPersonNum(contest.getPersonNum());
        adminVo.setStartDate(contest.getStartDate());
        return  adminVo;
    }
    /**
     * 新增竞赛
     *
     * @param webContest 竞赛
     * @return 结果
     */
    @Override
    public int insertWebContest(Contest webContest) {
        return this.baseMapper.insertWebContest(webContest);
    }

    /**
     * 修改竞赛
     *
     * @param contestDto 竞赛, 修改竞赛主键
     * @return 结果
     */
    @Override
    @Transactional
    public int updateWebContest(ContestDto contestDto, String id) {
        // 先将原本的竞赛删除
        if (id != null) {
            String[] ids = new String[]{id};
            this.baseMapper.deleteWebContestByIds(ids);
        }
        // 然后调用添加竞赛的逻辑将修改之后的竞赛添加进去
        boolean b = pushContest(contestDto);
        return b ? 1 : 0;
    }

    /**
     * 批量删除竞赛
     *
     * @param ids 需要删除的竞赛主键
     * @return 结果
     */
    @Override
    public int deleteWebContestByIds(String[] ids) {
        LambdaUpdateWrapper<Contest> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.in(Contest::getId, ids);
        updateWrapper.set(Contest::getIsDel, "1");
        return baseMapper.update(null, updateWrapper);
    }

    /**
     * 删除竞赛信息
     *
     * @param id 竞赛主键
     * @return 结果
     */
    @Override
    public int deleteWebContestById(String id) {
        return this.baseMapper.deleteWebContestById(id);
    }

    /**
     * 审核竞赛状态
     *
     * @param contestId 竞赛id
     * @param status    状态
     * @return int
     */
    @Override
    @Transactional
    public int checkContestStatus(String contestId, String status) {
        //1.查询数据库 找到对应的竞赛
        Contest contest = this.baseMapper.selectById(contestId);
        //2.将状态改成后台发送的状态
        contest.setStatus(status);
        //3.保存进入数据库
        return this.baseMapper.updateById(contest);
    }

    /**
     * 获取竞赛详情以及评论列表
     *
     * @param contestId 竞赛Id
     * @param type      评论类型
     * @return ContestAndComment
     */
    @Override
    public ContestAndCommentVo getContestInfoAndCommentList(String contestId, String type) {
        //1.根据竞赛id查询竞赛详情
        Contest contest = this.baseMapper.selectById(contestId);
        //使用封装的对象拷贝工具
        ContestAndCommentVo contestAndCommentVo = BeanCopyUtils.copyBean(contest, ContestAndCommentVo.class);
        //2.查询评论列表
        List<Comment> comments = commentMapper.selectList(Wrappers
                .<Comment>lambdaQuery()
                .eq(Comment::getActivityId, contestId)
                .eq(Comment::getType, type));
        //3.返回的是一个平铺的有关该竞赛的评论列表 需要准备成树形结构
        Map<String, List<Comment>> collect = comments.stream().collect(Collectors.groupingBy(Comment::getToCommentId));
        //4.封装一个递归的方法
        List<Comment> resultCommentList = buildCommentTree(collect, "0");
        contestAndCommentVo.setCommentList(resultCommentList);
        //5.新增浏览量
        redisCache.incrementCacheMapValue(ActivitysConstant.CONTEST_KEY, contestId, 1);
        return contestAndCommentVo;
    }


    /**
     * 构建评论列表
     *
     * @param collect 根据所回复的Id分组的平铺数据
     * @param pId     父id
     * @return List<Comment>
     */
    private List<Comment> buildCommentTree(Map<String, List<Comment>> collect, String pId) {
        return collect.getOrDefault(pId, new ArrayList<>()).stream().map(comment ->
                comment.setChildren(buildCommentTree(collect, comment.getId()))
        ).collect(Collectors.toList());
    }


    /**
     * 获取首页中的最热数据 8 条
     *
     * @return 竞赛List
     */
    @Override
    public List<Contest> queryIndexContest() {
        return this.baseMapper.selectIndexContest();
    }

    /**
     * 查询竞赛列表
     *
     * @param page       当前页码
     * @param limit      ，每页显示条数
     * @param categoryId 分类id
     * @return List<ContestListVo>
     */
    @Override
    public Map<String, Object> deskContestList(Long page, Long limit, String categoryId) {
        Page<Contest> contestPage = this.baseMapper.selectPage(new Page<>(page, limit),
                Wrappers.<Contest>lambdaQuery()
                        .eq(Contest::getStatus, "0")
                        .eq(StringUtils.isNotEmpty(categoryId), Contest::getCategoryId, categoryId));

        List<Contest> contestList = contestPage.getRecords().stream().filter(item -> {
            int result = DateUtils.differentDaysByMillisecond(item.getEnrollStartDate(), item.getEnrollEndDate());
            return result > 0 || result == 0;
        }).collect(Collectors.toList());

        List<ContestListVo> contestListVos = BeanCopyUtils.copyBeanList(contestList, ContestListVo.class);

        contestListVos.forEach(item -> item.setPour(DateUtils.differentDaysByMillisecond(item.getEnrollStartDate(),
                item.getEnrollEndDate())));

        Map<String, Object> map = new HashMap<>();
        map.put("total", contestList.size());
        map.put("rows", contestListVos);
        return map;
    }

    /**
     * 简易添加和修改
     * 竞赛标题, 竞赛描述, 允许参赛的人数, 竞赛地址, 竞赛开始时间, 竞赛结束时间
     * @param contestDto 要修改或者是添加的竞赛内容
     * @return
     */
    @Override
    public int addOrUpdateContestSimple(ContestDto contestDto) {
        /*查询数据库中是否存储在这条数据并且未删除, 如果存在那就是修改*/
        Contest oldContest = this.baseMapper.selectOne(Wrappers.
                <Contest>lambdaQuery(Contest.class)
                .eq(Contest::getId, contestDto.getId())
                .eq(Contest::getIsDel, "0"));

        /*检验分类信息的正确性*/
        if (categoryMapper.selectOne(Wrappers.<Category>lambdaQuery(Category.class).eq(Category::getId, contestDto.getCategoryId())) == null) {
            throw new RuntimeException("未知分类信息, 请重新选择分类");
        }

        /*将修改的字段重新赋值给他*/
        if (oldContest != null) {
            ContestDto newContestDto = assembleContestDto(oldContest);
            newContestDto.setTitle(contestDto.getTitle());
            newContestDto.setDescription(contestDto.getDescription());
            newContestDto.setCategoryId(contestDto.getCategoryId());
            newContestDto.setStartDate(contestDto.getStartDate());
            newContestDto.setEndDate(contestDto.getEndDate());
            newContestDto.setContent(contestDto.getContent());
            newContestDto.setLocal(contestDto.getLocal());
            newContestDto.setPersonNum(contestDto.getPersonNum());
            newContestDto.setCover(contestDto.getCover());
            contestDto = newContestDto;

            /*一切结束之后需要将原来的信息置为删除*/
            if (deleteWebContestById(contestDto.getId()) == 0) {
                throw new RuntimeException("删除信息失败");
            }
        }

        contestDto.setId(null);
        
        /*重新发布竞赛*/
//        return pushContest(contestDto) ? 1 : 0;

        //4.对象拷贝 将竞赛信息DTO拷贝进入实体类
        Contest contest = BeanCopyUtils.copyBean(contestDto, Contest.class);
        contest.setPushId(AuthContextHolder.getUserId()); //将当前用户id赋值给发布者id
        //如果不置顶是否为团队参赛直接默认为不是团队赛
        if (StringUtils.isEmpty(contest.getIsTeam())) {
            contest.setIsTeam("1"); //默认为不是团队参赛
        } else {
            contest.setTeamNum(contest.getTeamNum()); //设置参赛人数
        }
        contest.setParticipationNum(0L);
        contest.setViewNum(0);
        contest.setStatus("1"); //默认待审核状态
        contest.setIsTop("1");
        contest.setSort(-1); //代表没有设置
        contest.setPushTime(new Date()); //当前时间就是发布时间
        contest.setToll(contestDto.getToll() == null ? new BigDecimal("0") : contestDto.getToll());
        contest.setTeamNum(contestDto.getTeamNum() == null ? 0 : contestDto.getTeamNum());


        //顺便将发布者的姓名也存入数据库当中 这样可以防止很多麻烦的联表
        contest.setPushName("admin");
        //5. 保存数据库 交给后台进行审核
        boolean result = this.save(contest);

        //6. 如果发布者使用系统的报名模板那么需要把模板存入模板
        List<TemplateValue> teValue = contestDto.getTemplateValueList();
        String templateJson = JSONObject.toJSONString(teValue);

        //7.存入模板表中
        Template template = new Template();
        template.setTemplate(templateJson);
        template.setActivityId(contestDto.getId());
        template.setType(ActivityTypeConstants.CONTEST_TYPE);

        int eff = this.templateMapper.insert(template);

        return result && eff != 0 ? 1 : 0;
    }
}
